package fr.nghs.android.common;

/**
 * Utility and fast math functions.
 * 
 * Thanks to:<br>
 * Riven on JavaGaming.org for sin/cos/atan2/floor/ceil.<br>
 * Roquen on JavaGaming.org for random numbers.<br>
 */
public class MathUtils {
	static public final float PI = 3.1415927f;

	static private final int SIN_BITS = 13; // Adjust for accuracy.
	static private final int SIN_MASK = ~(-1 << SIN_BITS);
	static private final int SIN_COUNT = SIN_MASK + 1;

	static private final float radFull = PI * 2;
	static private final float degFull = 360;
	static private final float radToIndex = SIN_COUNT / radFull;
	static private final float degToIndex = SIN_COUNT / degFull;
	static public final float radiansToDegrees = 180f / PI;
	static public final float degreesToRadians = PI / 180;

	static public final float[] sin = new float[SIN_COUNT];
	//static public final float[] cos = new float[SIN_COUNT];
	static {
		for (int i = 0; i < SIN_COUNT; i++) {
			float a = (i + 0.5f) / SIN_COUNT * radFull;
			sin[i] = (float)Math.sin(a);
			//cos[i] = (float)Math.cos(a);
		}
	}

	static public final float sin (float rad) {
		return sin[(int)(rad * radToIndex) & SIN_MASK];
	}

	static public final float cos (float rad) {
		return sin[(int)( ((PI / 2.0f) - rad) * radToIndex) & SIN_MASK];
	}
	
	static public final float tan (float rad) {
		return sin(rad) / cos(rad);
	}

 	static public final float sinDeg (float deg) {
		return sin[(int)(deg * degToIndex) & SIN_MASK];
	}

	static public final float cosDeg (float deg) {
		return sin[(int)((90.0f - deg) * degToIndex) & SIN_MASK];
	}

	static private final int ATAN2_BITS = 7; // Adjust for accuracy.
	static private final int ATAN2_BITS2 = ATAN2_BITS << 1;
	static private final int ATAN2_MASK = ~(-1 << ATAN2_BITS2);
	static private final int ATAN2_COUNT = ATAN2_MASK + 1;
	static private final int ATAN2_DIM = (int)Math.sqrt(ATAN2_COUNT);
	static private final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1);
	static private final float[] atan2 = new float[ATAN2_COUNT];
	static {
		for (int i = 0; i < ATAN2_DIM; i++) {
			for (int j = 0; j < ATAN2_DIM; j++) {
				float x0 = (float)i / ATAN2_DIM;
				float y0 = (float)j / ATAN2_DIM;
				atan2[j * ATAN2_DIM + i] = (float)Math.atan2(y0, x0);
			}
		}
	}

	static public final float atan2 (float y, float x) {
		float add, mul;
		if (x < 0) {
			if (y < 0) {
				y = -y;
				mul = 1;
			} else
				mul = -1;
			x = -x;
			add = -3.141592653f;
		} else {
			if (y < 0) {
				y = -y;
				mul = -1;
			} else
				mul = 1;
			add = 0;
		}
		float invDiv = 1 / ((x < y ? y : x) * INV_ATAN2_DIM_MINUS_1);
		int xi = (int)(x * invDiv);
		int yi = (int)(y * invDiv);
		return (atan2[yi * ATAN2_DIM + xi] + add) * mul;
	}

	// ---

	static private int randomSeed = (int)System.currentTimeMillis();

	/**
	 * Returns a random number between 0 (inclusive) and the specified value (inclusive).
	 * @param range Must be >= 0.
	 */
	static public final int random (int range) {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return (seed >>> 15) * (range + 1) >>> 17;
	}

	static public final int random (int start, int end) {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return ((seed >>> 15) * (end - start + 1) >>> 17) + start;
	}

	static public final boolean randomBoolean () {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return seed > 0;
	}
	
	/**
	 * Returns a random boolean. 'true' has a probability of truePercent%
	 * @return
	 */
	static public final boolean randomBoolean(int truePercent)
	{
		return random(100) < truePercent;
	}

	static public final float random () {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return (seed >>> 8) * 1f / (1 << 24);
	}

	static public final float random (float range) {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return (seed >>> 8) * 1f / (1 << 24) * range;
	}

	static public final float random (float start, float end) {
		final int seed = randomSeed * 1103515245 + 12345;
		randomSeed = seed;
		return start + (seed >>> 8) * 1f / (1 << 24) * (end - start);
	}

	// ---

	static public final int nextPowerOfTwo (int value) {
		return 1 << 32 - Integer.numberOfLeadingZeros(value - 1);
	}

	static public final boolean isPowerOfTwo (int value) {
		return value != 0 && (value & value - 1) == 0;
	}

	// ---

    public static final float abs(final float x) {
        return x > 0 ? x : -x;
    }

    public static final float abs(final int x) {
        return x > 0 ? x : -x;
    }

    
	public static final int floor(final float x) {
		return x > 0 ? (int) x : (int) x - 1;
	}

	public static final int ceil(final float x){
		return floor(x+.5f);
	}

	public static final int round(final float x){
		return floor(x + .5f);
	}

	/** Ensure that value is in [min, max] */
	public final static int ensureRange(final int value, final int min, final int max)
	{
	    if(value < min)
	        return min;
	    else if(value > max)
	        return max;
	    else
	        return value;
	}
	
    public final static float ensureRange(final float value, final float min, final float max)
    {
        if(value < min)
            return min;
        else if(value > max)
            return max;
        else
            return value;
    }

	// Max/min rewritten here because for some reason MathUtils.max/min
	// can run absurdly slow for such simple functions...
	public final static float max(final float a, final float b) {
		return a > b ? a : b;
	}

	public final static int max(final int a, final int b) {
		return a > b ? a : b;
	}
	
    public final static long max(final long a, final long b) {
        return a > b ? a : b;
    }

	public final static float min(final float a, final float b) {
		return a < b ? a : b;
	}

	public final static int min(final int a, final int b) {
        return a < b ? a : b;
    }
	
    public final static long min(final long a, final long b) {
        return a < b ? a : b;
    }
	
	public final static float map(final float val, final float fromMin, final float fromMax,
			final float toMin, final float toMax) {
		final float mult = (val - fromMin) / (fromMax - fromMin);
		final float res = toMin + mult * (toMax - toMin);
		return res;
	}

	/** Returns the closest value to 'a' that is in between 'low' and 'high' */
	public final static float clamp(final float a, final float low, final float high) {
		return max(low, min(a, high));
	}



	/**
	 * Computes a fast approximation to <code>Math.pow(a, b)</code>.
	 * Adapted from <url>http://www.dctsystems.co.uk/Software/power.html</url>.
	 * 
	 * @param a
	 *            a positive number
	 * @param b
	 *            a number
	 * @return a^b
	 */
	// UNTESTED
	public static final float pow(final float a, float b) {
		// adapted from: http://www.dctsystems.co.uk/Software/power.html
		float x = Float.floatToRawIntBits(a);
		x *= 1.0f / (1 << 23);
		x = x - 127;
		float y = x - MathUtils.floor(x);
		b *= x + (y - y * y) * 0.346607f;
		y = b - MathUtils.floor(b);
		y = (y - y * y) * 0.33971f;
		return Float.intBitsToFloat((int) ((b + 127 - y) * (1 << 23)));

	}

	public static final float sqrt(float x) {
		if (x != 0.0f) { // thanks FrancescoITA for including the zero case, now it's technically slightly faster! and more accurate! woooo!

			x = invSqrt(x);

			return 1.0f / x;
		}
		else {
			return 0;
		}
	}

	public final static float invSqrt(float x) {
		final float xhalf = 0.5f * x;
		int i = Float.floatToRawIntBits(x);
		i = 0x5f3759df - (i >> 1);
		x = Float.intBitsToFloat(i);
		x *= 1.5f - xhalf * x * x;
		// REPEAT FOR ACCURACY (make sure at least 2 are here, too inaccurate
		// otherwise)
		x *= 1.5f - xhalf * x * x;
		x *= 1.5f - xhalf * x * x;
		x *= 1.5f - xhalf * x * x;
		return x;
	}
	
	/**
	 * Increment the value by one, wrap to 0 if it equals max
	 * @return
	 */
	public final static int incrementAndWrap(int value, int max)
	{
	    value++;
	    if(value == max)
	        return 0;
	    return value;
	}

	/**
	 * Decrement the value by one, wrap to max-1 if it equals -1
	 * @return
	 */
	public final static int decrementAndWrap(int value, int max)
	{
	    if(value == 0)
		return max - 1;
	    return value - 1;
	}

}
